package cn.gs.serializable.serialize;

// @author gs
import cn.gs.serializable.entity.IEntity;
import cn.gs.serializable.entity.MapEntity;
import cn.gs.serializable.entity.OneEntity;
import cn.gs.serializable.serialize.ISerialize;
import cn.gs.serializable.serialize.SerializeController;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ObjectSerialize implements ISerialize {

    static Method GET_CLASS_METHOD;
    Map<String, ISerialize> special = new HashMap<String, ISerialize>();

    public ObjectSerialize() {
    }

    public ObjectSerialize(Map<String, ISerialize> special) {
        this.special = special;
    }

    private ISerialize serialize(ISerialize ctrl, String name) {
        ISerialize i = special.get(name);
        return i == null ? ctrl : i;
    }

    @Override
    public <T> T toObj(ISerialize ctrl, IEntity from, Class<T> clazz) {
        if (!(from instanceof MapEntity)) {
            return null;
        }
        if (clazz == null) {
            return (T) from;
        }
        //Map MapEntity
        Map<String, IEntity> m = new HashMap<String, IEntity>();
        for (Entry<IEntity, IEntity> e : ((MapEntity) from).getMap().entrySet()) {
            String k = ctrl.toObj(ctrl, e.getKey(), String.class);
            m.put(k, e.getValue());
        }
        IEntity ce = m.get("getClass");
        if (ce != null) {
            Class c = ctrl.toObj(ctrl, ce, Class.class);
            if (c != null) {
                clazz = c;
            }
        }
        //Set properties
        BeanInfo infos;
        try {
            infos = Introspector.getBeanInfo(clazz, clazz.getSuperclass());
            T ins = clazz.newInstance();
            for (PropertyDescriptor pd : infos.getPropertyDescriptors()) {
                IEntity v = m.get(pd.getDisplayName());
                if (v != null && pd.getWriteMethod() != null && pd.getWriteMethod().getParameterTypes().length > 0) {
                    try {
                        Object vv = serialize(ctrl, pd.getDisplayName()).toObj(ctrl, v, pd.getWriteMethod().getParameterTypes()[0]);
                        pd.getWriteMethod().invoke(ins, vv);
                    } catch (Throwable ex) {
                        Logger.getLogger(ObjectSerialize.class.getName()).log(Level.INFO, null, ex);
                    }
                }
            }
            return ins;
        } catch (Throwable ex) {
            Logger.getLogger(ObjectSerialize.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }

    static {
        try {
            GET_CLASS_METHOD = Object.class.getDeclaredMethod("getClass");
        } catch (Throwable ex) {
            Logger.getLogger(ObjectSerialize.class.getName()).log(Level.SEVERE, null, ex);
            GET_CLASS_METHOD = null;
        }
    }

    @Override
    public IEntity fromObj(ISerialize ctrl, Object o) {
        if (o == null) {
            return null;
        }
        Map<IEntity, IEntity> map = new HashMap<IEntity, IEntity>();
        BeanInfo infos;
        try {
            infos = Introspector.getBeanInfo(o.getClass(), o.getClass().getSuperclass());
            for (PropertyDescriptor pd : infos.getPropertyDescriptors()) {
                try {
                    Method getm = pd.getReadMethod();
                    if (getm != null && (pd.getWriteMethod() != null || getm.equals(GET_CLASS_METHOD))) {
                        Object f = getm.invoke(o);
                        IEntity v = serialize(ctrl, pd.getDisplayName()).fromObj(ctrl, f);
                        IEntity k = new OneEntity(pd.getDisplayName(), true);
                        map.put(k, v);
                    }
                } catch (Throwable ex) {
                    Logger.getLogger(ObjectSerialize.class.getName()).log(Level.INFO, null, ex);
                }
            }
        } catch (IntrospectionException ex) {
            Logger.getLogger(ObjectSerialize.class.getName()).log(Level.WARNING, null, ex);
            return null;
        }
        return new MapEntity(map);
    }
}
